package design

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"gitee.com/bobo-rs/idea-space-framework/framework/model"
	"gitee.com/bobo-rs/idea-space-framework/framework/model/entity"
	"gitee.com/bobo-rs/idea-space-framework/framework/service"
	"gitee.com/bobo-rs/idea-space-framework/pkg/config"
	"gitee.com/bobo-rs/idea-space-framework/pkg/core/models"
	"gitee.com/bobo-rs/idea-space-framework/pkg/core/services/design"
	"gitee.com/bobo-rs/idea-space-framework/pkg/core/services/material"
	"gitee.com/bobo-rs/idea-space-framework/pkg/ifile"
	"gitee.com/bobo-rs/idea-space-framework/pkg/img"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gfile"
	"image"
	"io"
	"os"
	"path/filepath"
	"regexp"
)

const (
	CutWordsValueSep  = `[,，]`           // 切词分割符
	CutDesignImageNum = 5                // 切词图片数量
	OverlayDesignDir  = `design/overlay` // 设计合成图片文件目录
)

// OverlayDesignImage 合成并生成设计图片
func (d *sDesign) OverlayDesignImage(ctx context.Context, in model.OverlayDesignImageInput) (out *model.OverlayDesignImageOutput, err error) {
	// 通过标签值获取切分模板列表和卖点
	cutSelling, err := d.processCutSellingTemplateList(ctx, in)
	if err != nil {
		return nil, err
	}

	// 获取模板列表
	templateList, err := d.processMatchTemplateList(ctx, cutSelling.SellingValues, cutSelling.CutTemplate)
	if err != nil {
		return nil, err
	}

	// 读取底图文件
	underImag, err := d.ParseUnderFileImage(in.File)
	if err != nil {
		return nil, err
	}

	// 生成设计合成列表图片数据
	overlays, err := d.GenDesignOverlaysList(ctx, templateList, model.DesignOverlaysParamItem{
		Price:      in.Price,
		UnderImage: underImag,
		Filename:   filepath.Ext(in.File.Filename),
	})
	if err != nil {
		return nil, err
	}

	// 初始化
	out = &model.OverlayDesignImageOutput{
		Total: cutSelling.Total,
		Size:  CutDesignImageNum,
	}

	// 合成图片
	out.Rows, err = material.New().MaterialDesignOverlays(overlays...)
	if err != nil {
		return nil, err
	}
	// 匹配量加一
	_ = service.Material().IncMaterialTemplateMatchNum(ctx, cutSelling.CutTemplate.MatchTid)
	return
}

// DownloadDesignImage 下载并保存设计图片
func (d *sDesign) DownloadDesignImage(ctx context.Context, in model.DesignDownloadInput) (out *model.DesignDownloadOutput, err error) {
	// 保存下载图片数据
	item, err := d.ProcessDownloadDesignImage(ctx, in)
	if err != nil {
		return nil, err
	}

	// 初始化
	out = &model.DesignDownloadOutput{
		DesignDownloadItem: model.DesignDownloadItem{
			Src: item.Src,
		},
	}

	// 检查是否存在
	detail := entity.SpaceImagesAttr{}
	err = service.Space().ProcessAttrByAttachId(
		ctx, item.AttachId, &detail,
	)
	// 已存在直接返回
	if err == nil {
		out.SpaceId = detail.SpaceId
		return out, nil
	}

	sellingValues := d.SplitSelling(in.SellingValue)
	// 保存到设计库
	spaceItem := model.SpaceDetailItem{
		SpaceImageItem: model.SpaceImageItem{
			SpaceImages: entity.SpaceImages{
				SpaceName:   sellingValues[0], // 第一个截取卖点为名
				ImageUrl:    item.Src,
				DesignType:  uint(in.DesignType),
				DownloadNum: 1,
				CreatorId:   service.BizCtx().GetUid(ctx), // 创建人ID
			},
		},
		Attr: &model.SpaceImageAttrItem{
			AttachId:  item.AttachId,
			Width:     uint(item.Width),
			Height:    uint(item.Height),
			Size:      uint(item.Size),
			Precision: 5,
		},
		Detail: &model.SpaceImageDetailItem{
			TemplateId:   in.TemplateId,
			LabelValues:  in.LabelValues,
			FullCutWords: in.SellingValue,
			Synopsis:     in.SellingValue,
		},
	}
	// 保存数据
	spaceId, err := service.Space().SaveSpaceImage(ctx, spaceItem)
	if err != nil {
		return nil, gerror.Wrapf(err, `保存失败%s`, err.Error())
	}
	out.SpaceId = uint(spaceId)
	// 使用量加一
	if in.TemplateId > 0 {
		_ = service.Material().IncMaterialTemplateUseNum(ctx, []uint{in.TemplateId})
	}
	return out, nil
}

// ProcessDownloadDesignImage 处理并下载设计图片数据
func (d *sDesign) ProcessDownloadDesignImage(ctx context.Context, in model.DesignDownloadInput) (item *model.DesignImageDownloadItem, err error) {
	var (
		attachId   = ifile.FilenameMd5(in.Content)
		attachment = service.Attachment()
		detail     model.AttachmentBasicItem
	)

	// 解析图片文件后缀
	filename, err := ifile.ParseBase64ImageExt(in.Prefix, attachId)
	if err != nil {
		return nil, err
	}

	// 是否已下载
	err = attachment.ProcessDetailByAttachId(ctx, attachId, &detail)
	if err == nil {
		return &model.DesignImageDownloadItem{
			Src:      detail.AttUrl,
			Filename: detail.RealName,
			Width:    int(detail.Width),
			Height:   int(detail.Height),
			Size:     detail.AttSize,
			AttachId: detail.AttachId,
		}, nil
	}

	// 解析base64图片内容
	imgContent, err := img.ParseContentToImage(in.Content)
	if err != nil {
		return nil, err
	}

	// 图片保存
	imgHandle := img.New()
	imgHandle.SetImg(imgContent)

	// 文件名和保存位置
	_, dir := d.overlayDirOrName(filename)
	imgHandle.SetDir(ifile.MustUploadPath(dir))
	imgHandle.SetFilename(filename)

	// 保存图片
	err = imgHandle.Save()
	if err != nil {
		return nil, err
	}

	// 文件信息
	item = &model.DesignImageDownloadItem{
		Src:      ifile.SlashUploadPath(imgHandle.GetFilePath()),
		Filename: filename,
		AttachId: attachId,
	}
	// 高宽度
	item.Width, item.Height = imgHandle.ImgWH()

	// 读取文件大小
	file, err := os.Stat(ifile.MustUploadPath(item.Src))
	if err != nil {
		return nil, gerror.Wrapf(err, `读取文件信息报错%s`, err.Error())
	}
	item.Size = int(file.Size())

	// 生成Hash值
	hasher := sha256.New()
	_, _ = io.Copy(hasher, bytes.NewBuffer([]byte(in.Content)))

	// 保存附件
	sellingValues := d.SplitSelling(in.SellingValue)
	_, err = attachment.SaveAttachment(ctx, entity.Attachment{
		AttachId:  attachId,
		AttSize:   item.Size,
		RealName:  sellingValues[0],
		Name:      filename,
		Width:     uint(item.Width),
		Height:    uint(item.Height),
		AttPath:   item.Src,
		AttUrl:    item.Src,
		AttType:   `image/` + gfile.ExtName(filename),
		ChannelId: 1,
		Hasher:    hex.EncodeToString(hasher.Sum(nil)), // 生成hase值
	})
	if err != nil {
		return nil, gerror.Wrapf(err, `保存文件失败%s`, err.Error())
	}
	return
}

// GenDesignOverlaysList 生成设计合成列表图片数据
func (d *sDesign) GenDesignOverlaysList(ctx context.Context, templateList []model.MatchDesignTemplateItem, in model.DesignOverlaysParamItem) (overlays []models.MaterialDesignOverlayItem, err error) {
	// 字体配置
	fontPath, err := config.New().GetFontPath(ctx)
	if err != nil {
		return nil, err
	}
	overlays = make([]models.MaterialDesignOverlayItem, 0, len(templateList))
	// 迭代处理
	for _, template := range templateList {
		// 附件目录
		filename, dir := d.overlayDirOrName(in.Filename)
		item := models.MaterialDesignOverlayItem{
			TemplateId: template.Id,
			FontPath:   ifile.RelaResourcePath(fontPath),
			UnderImg:   in.UnderImage,
			MaterialDesignSaveItem: models.MaterialDesignSaveItem{
				IsSave:   in.SaveType == 1, // 1保存，0不保存
				Filename: filename,
				Dir:      ifile.MustUploadPath(dir),
			},
			DrawMaterialWords: make([]models.MaterialWordsItem, 0, len(template.Words)),
		}
		// 绘制文本
		for _, word := range template.Words {
			drawWord := models.MaterialWordsItem{
				Src:           ifile.RelaResourcePath(word.Url),
				Text:          word.ExampleWords,
				MaterialX:     word.MaterialX,
				MaterialY:     word.MaterialY,
				MaterialColor: word.MaterialColor,
				LayerPosition: word.LayerPosition,
				FontSize:      word.WordsFont,
				TextFontColor: word.WordsFontColor,
				TextX:         word.WordsX,
				TextY:         word.WordsY,
				TextType:      word.WordsType,
			}
			if word.WordsType == 2 {
				drawWord.Text = in.Price
			}
			item.DrawMaterialWords = append(item.DrawMaterialWords, drawWord)
		}
		// 组装文本
		overlays = append(overlays, item)
	}
	return overlays, nil
}

// ParseUnderFileImage 解析底图文件内容
func (d *sDesign) ParseUnderFileImage(file *ghttp.UploadFile) (img image.Image, err error) {
	// 读取底图文件
	fs, err := file.Open()
	if err != nil {
		return nil, err
	}
	defer func() {
		_ = fs.Close()
	}()

	// 转换为图片
	if img, _, err = image.Decode(fs); err != nil {
		return nil, gerror.Wrapf(err, `读取底图失败%s`, err.Error())
	}
	return
}

// processCutSellingTemplateList 通过切词卖点获取模板列表数据
func (d *sDesign) processCutSellingTemplateList(ctx context.Context, in model.OverlayDesignImageInput) (out *model.DesignCutSellingItem, err error) {
	// 通过标签值获取模板列表
	templates, err := service.Material().DesignTemplateMatchCacheList(ctx, model.DesignTemplateMatchCacheListInput{
		LabelValues: in.Values,
	})
	if err != nil {
		return nil, err
	}
	out = &model.DesignCutSellingItem{
		Total: len(templates.CutWordsList),
	}

	// 实例设计处理
	designHandler := design.New()

	// 拆分卖点词
	out.SellingValues = designHandler.SplitSellingValues(
		d.SplitSelling(in.Selling)...,
	)

	// 获取切词后的模板
	cutTemplate, err := designHandler.MatchCutTemplates(
		templates.CutWordsList, len(out.SellingValues), in.Page, CutDesignImageNum,
	)
	if err != nil {
		return nil, err
	}
	out.CutTemplate = *cutTemplate
	return
}

// processMatchTemplateList 处理并处理切词后模板列表
func (d *sDesign) processMatchTemplateList(ctx context.Context, sellingValues []models.DesignTemplateWordsItem, cutTemplate models.MatchDesignTemplateItem) (templateList []model.MatchDesignTemplateItem, err error) {
	// 实例设计处理
	designHandler := design.New()

	// 获取模板列表
	templateList, err = service.Material().GetMatchTemplateList(ctx, cutTemplate.MatchTid...)
	if err != nil {
		return nil, err
	}

	// 获取对应的模板文本数据
	templateWordsMap, err := service.Material().ProcessMaterialTemplateWordsMapsByTid(ctx, cutTemplate.MatchTid...)
	if err != nil {
		return nil, gerror.Wrapf(err, `模板文本不存在%s`, err.Error())
	}

	// 迭代处理数据
	for tKey, template := range templateList {
		words := templateWordsMap[template.Id]
		// 匹配数据
		sellingValuesMap := designHandler.MatchTemplateWordsBySelling(words.MatchWords, sellingValues)
		// 迭代匹配文本
		for key, item := range words.Words {
			// 卖点
			if selling, ok := sellingValuesMap[item.Id]; ok {
				item.ExampleWords = selling.Value
			}
			words.Words[key] = item
		}

		// 模板组装文本列表
		template.Words = words.Words
		templateList[tKey] = template
	}
	return
}

// overlayDirOrName 合成图片附件目录和文件名
func (d *sDesign) overlayDirOrName(originName string) (filename, dir string) {
	return ifile.GenFilename(originName), ifile.UploadSubDir(OverlayDesignDir, true)
}

// SplitSelling 根据分割符拆分卖点
func (d *sDesign) SplitSelling(selling string) []string {
	return regexp.MustCompile(CutWordsValueSep).Split(selling, -1)
}
